import time
import joblib
import smtplib
import sqlite3
import numpy as np
import pandas as pd
import streamlit as st
import plotly.express as px
import matplotlib.pyplot as plt
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import cross_val_score, GridSearchCV
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

# 多语言文本
text = {
    "zh": {
        "title": "设备故障数据分析系统",
        "description": "通过分析设备温度、振动值和故障标记数据，帮助识别高风险设备。",
        "filter_conditions": "筛选条件",
        "min_temp": "选择最低温度(℃)",
        "max_temp": "选择最高温度(℃)",
        "min_vibration": "选择最低振动值(mm/s)",
        "max_vibration": "选择最高振动值(mm/s)",
        "data_visualization": "数据可视化",
        "filtered_data": "筛选后的设备数据",
        "fault_rate": "筛选设备的故障率",
        "health_score": "设备健康评分",
        "health_score_distribution": "设备健康评分分布",
        "fault_probability": "设备故障概率",
        "fault_probability_distribution": "设备故障概率分布",
        "lifetime_prediction": "设备寿命预测",
        "lifetime_prediction_results": "设备寿命预测结果",
        "export_data": "导出筛选后的数据为 CSV",
        "export_chart": "导出健康评分图表为 PNG",
        "real_time_update": "启用实时数据更新（模拟）",
        "email_notification": "检查设备健康评分并发送邮件",
        "scheduled_check": "启动定时检查",
    },
    "en": {
        "title": "Equipment Failure Data Analysis System",
        "description": "Identify high-risk equipment by analyzing temperature, vibration, and failure data.",
        "filter_conditions": "Filter Conditions",
        "min_temp": "Select Minimum Temperature(℃)",
        "max_temp": "Select Maximum Temperature(℃)",
        "min_vibration": "Select Minimum Vibration(mm/s)",
        "max_vibration": "Select Maximum Vibration(mm/s)",
        "data_visualization": "Data Visualization",
        "filtered_data": "Filtered Equipment Data",
        "fault_rate": "Fault Rate of Filtered Equipment",
        "health_score": "Equipment Health Score",
        "health_score_distribution": "Equipment Health Score Distribution",
        "fault_probability": "Equipment Fault Probability",
        "fault_probability_distribution": "Equipment Fault Probability Distribution",
        "lifetime_prediction": "Equipment Lifetime Prediction",
        "lifetime_prediction_results": "Equipment Lifetime Prediction Results",
        "export_data": "Export Filtered Data as CSV",
        "export_chart": "Export Health Score Chart as PNG",
        "real_time_update": "Enable Real-Time Data Update (Simulation)",
        "email_notification": "Check Health Scores and Send Email",
        "scheduled_check": "Start Scheduled Check",
    }
}

# 语言切换
language = st.sidebar.selectbox("选择语言 / Select Language", ["中文", "English"])
lang = "zh" if language == "中文" else "en"


# 用户认证功能
def check_password():
    """检查用户名和密码是否正确"""
    def password_entered():
        # 检查输入的用户名和密码是否正确
        if (st.session_state["username"] in st.secrets["passwords"]
            and st.session_state["password"] == st.secrets["passwords"][st.session_state["username"]]):
            st.session_state["password_correct"] = True
            del st.session_state["password"]  # 不要保存密码
            del st.session_state["username"]
        else:
            st.session_state["password_correct"] = False

    # 如果是第一次运行，初始化 session_state
    if "password_correct" not in st.session_state:
        st.session_state["password_correct"] = False

    # 如果密码正确，直接返回 True
    if st.session_state["password_correct"]:
        return True

    # 显示登录表单
    st.text_input("用户名", key="username")
    st.text_input("密码", type="password", key="password")
    st.button("登录", on_click=password_entered)

    # 如果密码错误，显示错误信息
    if "password" in st.session_state and not st.session_state["password_correct"]:
        st.error("用户名或密码错误")

    return st.session_state["password_correct"]

# 检查用户认证
if not check_password():
    st.stop()  # 如果未通过认证，停止运行

# 页面标题和说明
st.title("设备故障数据分析系统")
st.markdown("""
    通过分析设备温度、振动值和故障标记数据，帮助识别高风险设备。
    - **筛选条件**：根据温度和振动值筛选设备。
    - **数据可视化**：查看设备温度、振动值分布及其关系。
    - **设备寿命预测**：预测设备剩余寿命。
    - **健康评分**：根据温度和振动值为设备打分。
""")

# 加载随机森林模型
try:
    rf_model = joblib.load('random_forest_model.pkl')
    st.success("随机森林模型已加载")
except FileNotFoundError:
    st.error("未找到模型文件，请先训练模型")



# 创建或连接 SQLite 数据库
conn = sqlite3.connect('设备数据.db')
cursor = conn.cursor()

# 创建表（如果不存在）
cursor.execute('''
CREATE TABLE IF NOT EXISTS 设备数据 (
    设备ID TEXT,
    温度_℃ REAL,
    振动值_mm_s REAL,
    故障标记 INTEGER,
    运行时间_小时 REAL
)
''')

# 创建反馈表（如果不存在）
cursor.execute('''
CREATE TABLE IF NOT EXISTS 设备反馈 (
    设备ID TEXT,
    检查结果 TEXT,
    备注 TEXT,
    提交时间 TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()

# 插入示例数据（如果表为空）
cursor.execute("SELECT COUNT(*) FROM 设备数据")
if cursor.fetchone()[0] == 0:
    data = [
        ('Device_1', 78, 0.39, 0, 500),
        ('Device_2', 61, 0.76, 0, 800),
        ('Device_3', 92, 1.09, 1, 200)
    ]
    cursor.executemany('INSERT INTO 设备数据 VALUES (?, ?, ?, ?, ?)', data)
    conn.commit()

# 从数据库读取数据
df = pd.read_sql('SELECT * FROM 设备数据', conn)

# 关闭数据库连接
conn.close()

# 数据清洗（处理缺失值）
df['温度_℃'].fillna(df['温度_℃'].mean(), inplace=True)
df['振动值_mm_s'].fillna(df['振动值_mm_s'].mean(), inplace=True)
df['运行时间_小时'].fillna(df['运行时间_小时'].mean(), inplace=True)

# 计算设备健康评分
def calculate_health_score(temp, vibration):
    # 温度越高，分数越低
    temp_score = max(0, 100 - (temp - 50) * 2)  # 假设 50℃ 是理想温度
    # 振动值越高，分数越低
    vibration_score = max(0, 100 - vibration * 50)  # 假设 1.0 mm/s 是理想振动值
    # 综合评分
    return (temp_score + vibration_score) / 2

# 添加健康评分列
df['健康评分'] = df.apply(lambda row: calculate_health_score(row['温度_℃'], row['振动值_mm_s']), axis=1)

# 分栏布局
col1, col2 = st.columns(2)

# 第一栏：筛选条件
with col1:
    st.header("筛选条件")
    min_temp = st.slider("选择最低温度(℃)", float(df['温度_℃'].min()), float(df['温度_℃'].max()), key="min_temp")
    max_temp = st.slider("选择最高温度(℃)", float(df['温度_℃'].min()), float(df['温度_℃'].max()), key="max_temp")
    min_vibration = st.slider("选择最低振动值(mm/s)", float(df['振动值_mm_s'].min()), float(df['振动值_mm_s'].max()), key="min_vibration")
    max_vibration = st.slider("选择最高振动值(mm/s)", float(df['振动值_mm_s'].min()), float(df['振动值_mm_s'].max()), key="max_vibration")

    # 根据筛选条件生成 filtered_df
    filtered_df = df[(df['温度_℃'] >= min_temp) & (df['温度_℃'] <= max_temp) &
                     (df['振动值_mm_s'] >= min_vibration) & (df['振动值_mm_s'] <= max_vibration)]

# 第二栏：数据可视化
with col2:
    st.header("数据可视化")
    st.write("筛选后的设备数据：", filtered_df)
    
    # 计算故障率
    fault_rate = filtered_df['故障标记'].mean()
    st.write(f"筛选设备的故障率：{fault_rate:.2%}")

# 数据可视化
st.header("数据可视化")
# 热力图
st.subheader("设备温度与振动值热力图")
heatmap_data = df.pivot_table(index='温度_℃', columns='振动值_mm_s', values='故障标记', aggfunc='mean')
fig_heatmap = px.imshow(
    heatmap_data,
    labels=dict(x="振动值(mm/s)", y="温度(℃)", color="故障标记"),
    title="设备温度与振动值热力图"
)
st.plotly_chart(fig_heatmap)

# 箱线图
st.subheader("设备温度、振动值和健康评分箱线图")
fig_box = px.box(
    df,
    y=['温度_℃', '振动值_mm_s', '健康评分'],
    title="设备温度、振动值和健康评分箱线图"
)
st.plotly_chart(fig_box)

# 交互式散点图
st.subheader("交互式散点图")
x_axis = st.selectbox("选择 X 轴特征", ['温度_℃', '振动值_mm_s', '运行时间_小时'])
y_axis = st.selectbox("选择 Y 轴特征", ['温度_℃', '振动值_mm_s', '运行时间_小时'])
fig_scatter = px.scatter(
    df,
    x=x_axis,
    y=y_axis,
    color='故障标记',
    title=f"{x_axis} 与 {y_axis} 的关系"
)
st.plotly_chart(fig_scatter)


# 自定义图表类型
st.subheader("自定义图表类型")
chart_type = st.selectbox("选择图表类型", ["柱状图", "折线图", "散点图"])

if chart_type == "柱状图":
    fig = px.bar(df, x='设备ID', y='温度_℃', title="设备温度柱状图")
elif chart_type == "折线图":
    fig = px.line(df, x='设备ID', y='温度_℃', title="设备温度折线图")
elif chart_type == "散点图":
    fig = px.scatter(df, x='设备ID', y='温度_℃', title="设备温度散点图")

st.plotly_chart(fig)

# 更多筛选条件
st.subheader("更多筛选条件")
selected_device = st.multiselect("选择设备 ID", df['设备ID'].unique())
min_runtime = st.slider("选择最低运行时间(小时)", float(df['运行时间_小时'].min()), float(df['运行时间_小时'].max()), float(df['运行时间_小时'].min()))
max_runtime = st.slider("选择最高运行时间(小时)", float(df['运行时间_小时'].min()), float(df['运行时间_小时'].max()), float(df['运行时间_小时'].max()))

filtered_df = df[
    (df['设备ID'].isin(selected_device)) &
    (df['运行时间_小时'] >= min_runtime) &
    (df['运行时间_小时'] <= max_runtime)
]

st.write("筛选后的设备数据：", filtered_df)

# 动态参数调整
st.subheader("动态参数调整")
color = st.color_picker("选择图表颜色", "#1f77b4")

if chart_type == "散点图":
    point_size = st.slider("选择点大小", 1, 10, 5)

if chart_type == "柱状图":
    fig = px.bar(df, x='设备ID', y='温度_℃', title="设备温度柱状图", color_discrete_sequence=[color])
elif chart_type == "折线图":
    fig = px.line(df, x='设备ID', y='温度_℃', title="设备温度折线图", color_discrete_sequence=[color])
elif chart_type == "散点图":
    fig = px.scatter(df, x='设备ID', y='温度_℃', title="设备温度散点图", color_discrete_sequence=[color], size_max=point_size)

st.plotly_chart(fig)


# 温度分布直方图
st.subheader("设备温度分布")
fig1, ax1 = plt.subplots()
ax1.hist(filtered_df['温度_℃'], bins=20, edgecolor='black')
ax1.set_xlabel('温度(℃)')
ax1.set_ylabel('设备数量')
st.pyplot(fig1)

# 振动值分布直方图
st.subheader("设备振动值分布")
fig2, ax2 = plt.subplots()
ax2.hist(filtered_df['振动值_mm_s'], bins=20, edgecolor='black')
ax2.set_xlabel('振动值(mm/s)')
ax2.set_ylabel('设备数量')
st.pyplot(fig2)

# 温度与振动值关系图
st.subheader("温度与振动值的关系")
fig3 = px.scatter(filtered_df, x='温度_℃', y='振动值_mm_s', color='故障标记',
                  title='温度与振动值的关系（红色为故障设备）')
st.plotly_chart(fig3)

# 设备健康评分分布图
st.subheader("设备健康评分分布")
fig5 = px.histogram(df, x='健康评分', nbins=20, title='设备健康评分分布')
st.plotly_chart(fig5)

# 设备故障趋势图
st.subheader("设备故障趋势")
# 假设数据中有“运行时间_小时”字段
df['运行时间_小时'] = pd.to_numeric(df['运行时间_小时'])  # 确保数据类型正确
df['故障标记'] = pd.to_numeric(df['故障标记'])  # 确保数据类型正确

# 按运行时间分组，计算故障率
fault_trend = df.groupby('运行时间_小时')['故障标记'].mean().reset_index()

# 绘制趋势图
fig6 = px.line(fault_trend, x='运行时间_小时', y='故障标记', title='设备故障率随时间变化趋势')
st.plotly_chart(fig6)


# 设备寿命预测 （优化机器学习模型）
st.header("设备寿命预测")

# 动态生成样本数据
if len(df) < 10:  # 如果样本数量少于 10，生成更多样本
    st.warning("样本数量不足，正在动态生成样本数据...")
    num_samples = 10  # 生成 10 个样本
    new_data = {
        '设备ID': [f'Device_{i}' for i in range(len(df) + 1, len(df) + num_samples + 1)],
        '温度_℃': np.random.randint(50, 100, num_samples),
        '振动值_mm_s': [np.round(np.random.uniform(0.1, 2.0), 2) for _ in range(num_samples)],
        '故障标记': np.random.choice([0, 1], num_samples),
        '运行时间_小时': np.random.randint(100, 1000, num_samples)
    }
    new_df = pd.DataFrame(new_data)
    df = pd.concat([df, new_df], ignore_index=True)
    st.write("新增样本数据：", new_df)


# 特征工程
X = df[['温度_℃', '振动值_mm_s', '运行时间_小时', '故障标记']]  # 添加更多特征
y = df['运行时间_小时']

# 标准化特征
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 随机森林模型
rf_model = RandomForestRegressor(random_state=42)
rf_scores = cross_val_score(rf_model, X_scaled, y, cv=5, scoring='r2')
st.write(f"随机森林模型的 R² 分数：{rf_scores.mean():.2f}")

# 训练随机森林模型
rf_model.fit(X_scaled, y)

# 保存模型和标准化器
joblib.dump(rf_model, 'random_forest_model.pkl')
joblib.dump(scaler, 'scaler.pkl')
st.success("模型和标准化器已保存")

# 实时预测
st.subheader("实时设备寿命预测")

# 加载随机森林模型和标准化器
try:
    rf_model = joblib.load('random_forest_model.pkl')
    scaler = joblib.load('scaler.pkl')
    st.success("模型和标准化器已加载")
except FileNotFoundError:
    st.error("未找到模型或标准化器文件，请先训练模型")

# 用户输入特征
st.write("请输入设备特征：")
temp = st.number_input("温度(℃)", min_value=0.0, max_value=150.0, value=50.0)
vibration = st.number_input("振动值(mm/s)", min_value=0.0, max_value=5.0, value=1.0)
runtime = st.number_input("运行时间(小时)", min_value=0.0, max_value=10000.0, value=500.0)
fault_flag = st.selectbox("故障标记", [0, 1])

# 标准化特征
input_data = scaler.transform([[temp, vibration, runtime, fault_flag]])

# 预测设备寿命
predicted_lifetime = rf_model.predict(input_data)[0]
st.write(f"预测的设备寿命：{predicted_lifetime:.2f} 小时")


# 梯度提升树模型
gb_model = GradientBoostingRegressor(random_state=42)
gb_scores = cross_val_score(gb_model, X_scaled, y, cv=5, scoring='r2')
st.write(f"梯度提升树模型的 R² 分数：{gb_scores.mean():.2f}")

# 训练随机森林模型
rf_model.fit(X_scaled, y)
y_pred_rf = rf_model.predict(X_scaled)

# 计算评估指标
mae_rf = mean_absolute_error(y, y_pred_rf)
rmse_rf = np.sqrt(mean_squared_error(y, y_pred_rf))  # 手动计算 RMSE
r2_rf = r2_score(y, y_pred_rf)

st.write(f"随机森林模型的评估指标：")
st.write(f"- 平均绝对误差 (MAE): {mae_rf:.2f}")
st.write(f"- 均方根误差 (RMSE): {rmse_rf:.2f}")
st.write(f"- R² 分数: {r2_rf:.2f}")

# 随机森林超参数调优
param_grid = {
    'n_estimators': [50, 100, 200],
    'max_depth': [None, 10, 20],
    'min_samples_split': [2, 5, 10]
}
grid_search = GridSearchCV(rf_model, param_grid, cv=5, scoring='r2')
grid_search.fit(X_scaled, y)

st.write(f"最佳超参数：{grid_search.best_params_}")
st.write(f"最佳模型的 R² 分数：{grid_search.best_score_:.2f}")


# 训练线性回归模型
X = df[['温度_℃', '振动值_mm_s']]
y = df['运行时间_小时']
model = LinearRegression()
model.fit(X, y)

# 预测设备寿命
df['预测寿命_小时'] = model.predict(X)

# 折叠设备寿命预测内容
with st.expander("查看设备寿命预测结果"):
    st.write("设备寿命预测结果：")
    st.write(df[['设备ID', '温度_℃', '振动值_mm_s', '预测寿命_小时']])
    fig4 = px.scatter(df, x='温度_℃', y='振动值_mm_s', color='预测寿命_小时',
                      title='设备寿命预测（颜色越深寿命越短）')
    st.plotly_chart(fig4)
    
# 数据导出功能
st.sidebar.header("数据导出")
if st.sidebar.button("导出筛选后的数据为 CSV"):
    filtered_df.to_csv('筛选后的设备数据.csv', index=False)
    st.sidebar.success("数据已导出为 '筛选后的设备数据.csv'")
if st.sidebar.button("导出健康评分图表为 PNG"):
    fig5.write_image("健康评分分布.png")
    st.sidebar.success("图表已导出为 '健康评分分布.png'")

# 模拟实时数据更新
if st.sidebar.checkbox("启用实时数据更新（模拟）"):
    st.write("实时数据更新中...")
    new_data = {
        '设备ID': [f'Device_{len(df) + 1}'],
        '温度_℃': [np.random.randint(50, 100)],
        '振动值_mm_s': [np.round(np.random.uniform(0.1, 2.0), 2)],
        '故障标记': [np.random.choice([0, 1])],
        '运行时间_小时': [np.random.randint(100, 1000)]
    }
    new_df = pd.DataFrame(new_data)
    df = pd.concat([df, new_df], ignore_index=True)
    st.write("新增设备数据：", new_df)

    # 动态刷新图表
    st.experimental_rerun()



# 邮件发送函数
def send_email(subject, body, to_email):
    # 邮件配置（从 secrets.toml 中读取）
    smtp_server = st.secrets["email"]["smtp_server"]
    smtp_port = st.secrets["email"]["smtp_port"]
    sender_email = st.secrets["email"]["sender_email"]
    sender_password = st.secrets["email"]["sender_password"]

    # 创建邮件内容
    msg = MIMEMultipart()
    msg["From"] = sender_email
    msg["To"] = to_email
    msg["Subject"] = subject
    msg.attach(MIMEText(body, "plain"))

    # 发送邮件
    try:
        with smtplib.SMTP(smtp_server, smtp_port) as server:
            server.starttls()  # 启用 TLS 加密
            server.login(sender_email, sender_password)
            server.sendmail(sender_email, to_email, msg.as_string())
        st.success("邮件发送成功！")
    except Exception as e:
        st.error(f"邮件发送失败：{e}")

# 检查设备健康评分并发送邮件
def check_health_scores(df):
    threshold = 50  # 健康评分阈值
    for index, row in df.iterrows():
        if row["健康评分"] < threshold:
            subject = f"设备 {row['设备ID']} 健康评分过低"
            body = f"设备 {row['设备ID']} 的健康评分为 {row['健康评分']}，低于阈值 {threshold}。请及时检查！"
            to_email = st.secrets["email"]["receiver_email"]  # 接收邮件的地址
            send_email(subject, body, to_email)

# 在仪表盘中添加邮件发送按钮
if st.button("检查设备健康评分并发送邮件"):
    check_health_scores(df)

# 定时检查设备健康评分并发送邮件
def schedule_health_check(interval_minutes=5):
    st.write(f"定时检查已启动，每隔 {interval_minutes} 分钟检查一次设备健康评分。")
    while True:
        check_health_scores(df)  # 检查设备健康评分并发送邮件
        time.sleep(interval_minutes * 60)  # 将分钟转换为秒

# 在仪表盘中添加定时检查按钮
if st.button("启动定时检查"):
    schedule_health_check()

# 记录已发送邮件的设备
sent_emails = set()

def check_health_scores(df):
    threshold = 50  # 健康评分阈值
    for index, row in df.iterrows():
        if row["健康评分"] < threshold and row["设备ID"] not in sent_emails:
            subject = f"设备 {row['设备ID']} 健康评分过低"
            body = f"设备 {row['设备ID']} 的健康评分为 {row['健康评分']}，低于阈值 {threshold}。请及时检查！"
            to_email = st.secrets["email"]["receiver_email"]  # 接收邮件的地址
            send_email(subject, body, to_email)
            sent_emails.add(row["设备ID"])  # 记录已发送邮件的设备

# 通知功能增强
st.subheader("通知功能增强")

# 自定义通知阈值
threshold = st.slider("设置健康评分阈值", 0, 100, 50)

# 选择通知方式
notification_method = st.selectbox("选择通知方式", ["邮件", "短信", "消息推送"])

# 检查设备健康评分并发送通知
if st.button("检查设备健康评分并发送通知"):
    for index, row in df.iterrows():
        if row["健康评分"] < threshold:
            subject = f"设备 {row['设备ID']} 健康评分过低"
            body = f"设备 {row['设备ID']} 的健康评分为 {row['健康评分']}，低于阈值 {threshold}。请及时检查！"
            
            if notification_method == "邮件":
                to_email = st.secrets["email"]["receiver_email"]
                send_email(subject, body, to_email)
            elif notification_method == "短信":
                st.warning("短信通知功能尚未实现")
            elif notification_method == "消息推送":
                st.warning("消息推送功能尚未实现")

    st.success("通知已发送！")

# 用户反馈功能
st.header("用户反馈")

# 选择设备
device_id = st.selectbox("选择设备", df['设备ID'].unique())

# 输入检查结果和备注
check_result = st.selectbox("检查结果", ["正常", "异常"])
remark = st.text_area("备注")

# 提交反馈
if st.button("提交反馈"):
    # 连接数据库
    conn = sqlite3.connect('设备数据.db')
    cursor = conn.cursor()

    # 插入反馈数据
    cursor.execute('''
    INSERT INTO 设备反馈 (设备ID, 检查结果, 备注)
    VALUES (?, ?, ?)
    ''', (device_id, check_result, remark))
    conn.commit()
    conn.close()

    st.success("反馈已提交！")

# 展示反馈数据
st.subheader("用户反馈记录")

# 连接数据库
conn = sqlite3.connect('设备数据.db')

# 读取反馈数据
feedback_df = pd.read_sql('SELECT * FROM 设备反馈', conn)

# 关闭数据库连接
conn.close()

# 展示反馈数据
st.write(feedback_df)